/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.jeff.springWebTestNG.proxy;

import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.CtNewMethod;
import javassist.NotFoundException;

/**
 *
 * @author winnie
 */
public class JavaassistPlayer {
    private static final Logger LOG = Logger.getLogger(JavaassistPlayer.class.getName());
    
    /**
     * This bean factory simulates the behavior of many frameworks, which is using javaassist to enhance
     * the managed beans (e.g. attached entity in Hibernate, component bean in Spring, etc.)
     */
    private static class ProxiedBeanFactory {
        private static Map<String, Class> proxyMap = new HashMap<String, Class>();
        static Object getBean(String beanName) {
            try {
                return proxyMap.get(beanName).newInstance();
            } catch (InstantiationException ex) {
                Logger.getLogger(ProxiedBeanFactory.class.getName()).log(Level.SEVERE, null, ex);
            } catch (IllegalAccessException ex) {
                Logger.getLogger(ProxiedBeanFactory.class.getName()).log(Level.SEVERE, null, ex);
            }
            return null;
        }
        
        static void registerBean(String beanName, Class proxyBeanClass) {
            proxyMap.put(beanName, proxyBeanClass);
        }
    }
    
    public void addPerformanceAopToMethod() {
        //cannot directly use NormalJavaClass.class, otherwise ctClass.toClass() will fail due to 
        //the restriction of classloader: 2 same name classes cannot be loaded in one classloader.
        //Here we arbitrary set the target class name, actually we can make use of a technology 
        //called "package scanning", referring to MyBeanUtil.scanClassesByPackage()
        String targetClassName = "com.jeff.springWebTestNG.proxy.NormalJavaClass";
        String[] targetMethods = {"letsDance", "letsDanceWithSomeParams"};
        try {
            CtClass ctClass = ClassPool.getDefault().get(targetClassName);//NormalJavaClass.class.getCanonicalName());
//            for (Method method : NormalJavaClass.class.getDeclaredMethods()) {
            for (String origName : targetMethods) {
//                String origName = "letsDance";//method.getName();
                CtMethod mold = ctClass.getDeclaredMethod(origName);
                mold.setName(mold.getName() + "$javaassit_proxy_0");
                
                //create a new method with the name of the original method(which known by the innocent API user
                CtMethod proxiedMold = CtNewMethod.copy(mold, origName, ctClass, null);
                //prepare the method body (java source code) to define its behavior as you wish
                StringBuilder body = new StringBuilder();
                body.append("{\n");
                body.append("long start = System.currentTimeMillis();\n");
                body.append(mold.getName() + "($$);\n");//$$ means any parameters
                body.append("long end = System.currentTimeMillis();\n");
                body.append("System.out.println(\"Execution time is \" + (end - start) + \" millisecs.\");\n");
                body.append("}");
                proxiedMold.setBody(body.toString());
                
                //add the new method (which name is same as the actual method we want to add proxy)
                ctClass.addMethod(proxiedMold);
            }
            //take a preview of the methods we already have
            for (CtMethod method : ctClass.getDeclaredMethods()) {
                LOG.log(Level.INFO, "Current method: *** {0}", method.getName());
            }
            
            //let's test the result
            ProxiedBeanFactory.registerBean(targetClassName, ctClass.toClass());
            NormalJavaClass obj = (NormalJavaClass) ProxiedBeanFactory.getBean(targetClassName);
            obj.letsDance();
            NormalJavaClass obj1 = (NormalJavaClass) ProxiedBeanFactory.getBean(targetClassName);
            obj1.letsDanceWithSomeParams("Error", 103);
        } catch (NotFoundException ex) {
            LOG.log(Level.SEVERE, null, ex);
        } catch (CannotCompileException ex) {
            LOG.log(Level.SEVERE, null, ex);
        } 
    }
}
